/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "wfs_api.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LOG_TAG "WFS-API"

#include "include.h"
#include "def.h"
#include "util.h"
#include "log.h"

#define DEFAULT_MSG_BODY_LEN 128

/**
 * struct wfs_handle -- Handle created for user, but is only exposed internally
 */
struct wfs_handle {
	/** Interface name */
	char ifname[IFNAMSIZ + 1];
	/** Command socket */
	int cmd_sock;
	/** Command socket's local address */
	struct sockaddr_in cmd_local;
	/** Command socket's destination address */
	struct sockaddr_in cmd_dest;
	/** Event socket */
	int evt_sock;
	/** Event socket's local address */
	struct sockaddr_in evt_local;
	/** Event socket's destination address */
	struct sockaddr_in evt_dest;
};

/***********************************************************
 *                     Local functions
 ***********************************************************
 */

static struct wfs_msg *wfs_create_msg(const char *body)
{
	struct wfs_msg *ret = NULL;

	assert(body);

	ret = (struct wfs_msg *) zalloc(sizeof(*ret));
	if (!ret)
		return NULL;

	ret->len = strlen(body) + 1;
	ret->body = (char *) zalloc(ret->len);
	if (!ret->body) {
		FREE(ret);
		return NULL;
	}

	memcpy(ret->body, body, ret->len);

	return ret;
}


static struct wfs_msg *wfs_create_default_msg(void)
{
	struct wfs_msg *ret = NULL;
	ret = (struct wfs_msg *) zalloc(sizeof(*ret));
	if (!ret)
		return NULL;

	ret->len = DEFAULT_MSG_BODY_LEN;
	ret->body = (char *) zalloc(ret->len);
	if (!ret->body) {
		FREE(ret);
		return NULL;
	}

	memset(ret->body, 0, ret->len);

	return ret;
}


static char *wfs_api_err_to_str(enum wfs_error err)
{
	switch (err) {
	case WFS_ERR_SUCCESS:
		return "Success.";
	case WFS_ERR_INVAL:
		return "The input info is invalid.";
	case WFS_ERR_OOM:
		return "Out of memory.";
	case WFS_ERR_OS:
		return "Error happens when communicating with OS.";
	default:
		break;
	}

	return "Unknown error";
}


static enum wfs_error _wfs_send(int sock, const struct wfs_msg *cmd,
			struct wfs_msg *resp)
{
	enum wfs_error ret = WFS_ERR_SUCCESS;
	struct timeval tv;
	fd_set readfds;
	char buf[LARGE_BUF_LEN];
	int recv_cnt = 0;

	assert(cmd);

	ENTER();

	if (send(sock, cmd->body, cmd->len, 0) < 0) {
		wfs_perror("[cmd_sock] send");
		ret = WFS_ERR_OS;
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto out;
	}

	for (;;) {
		FD_ZERO(&readfds);
		FD_SET(sock, &readfds);
		tv.tv_sec  = 10;
		tv.tv_usec =  0;
		if (select(sock + 1, &readfds, NULL, NULL, &tv) < 0) {
			wfs_perror("[sock] select");
			ret = WFS_ERR_OS;
			wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
			goto out;
		}
		if (FD_ISSET(sock, &readfds)) {
			memset(buf, 0, sizeof(buf));
			recv_cnt = recv(sock, buf, sizeof(buf) - 1, 0);
			if (recv_cnt < 0) {
				wfs_perror("[sock] recv");
				ret = WFS_ERR_OS;
				wfs_printf(LOG_ERR, "%s",
					wfs_api_err_to_str(ret));
				goto out;
			}
			if (resp) {
				resp->len = MIN(resp->len,
						(unsigned int) recv_cnt + 1);
				memcpy(resp->body, buf, resp->len);
				resp->body[resp->len - 1] = 0;
			}
			break;
		}
	}

out:
	LEAVE();
	return ret;
}


static enum wfs_error _wfs_recv(int sock, struct wfs_msg *event)
{
	enum wfs_error ret = WFS_ERR_SUCCESS;
	int recv_cnt = 0;

	assert(event);

	ENTER();

	memset(event->body, 0, event->len);
	recv_cnt = recv(sock, event->body, event->len - 1, 0);
	if (recv_cnt < 0) {
		wfs_perror("[evt_sock] recv");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto out;
	}
	event->len = (event->len < recv_cnt) ? event->len : recv_cnt;
	event->body[event->len - 1] = 0;

out:
	LEAVE();
	return ret;
}


/***********************************************************
 *                     Global functions
 ***********************************************************
 */

struct wfs_handle *wfs_connect(const struct wfs_handle_params *params)
{
	struct wfs_handle *handle = NULL;
	struct wfs_msg *cmd = NULL;
	struct wfs_msg *resp = NULL;
	char buf[DEFAULT_MSG_BODY_LEN];
	enum wfs_error ret = WFS_ERR_SUCCESS;

	ENTER();

	if (!params) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_INVAL));
		goto out;
	}

	handle = (struct wfs_handle *) zalloc(sizeof(*handle));
	if (!handle) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OOM));
		goto out;
	}

	memset(buf, 0, sizeof(buf));

	/** Interface name */
	strncpy(handle->ifname, params->ifname, IFNAMSIZ);

	/** Command socket */
	handle->cmd_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (handle->cmd_sock < 0) {
		wfs_perror("[cmd_sock] socket");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto fail;
	}

	handle->cmd_local.sin_family = AF_INET;
	handle->cmd_local.sin_port = 0;
	handle->cmd_local.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(handle->cmd_sock, (struct sockaddr *) &handle->cmd_local,
			sizeof(handle->cmd_local)) < 0) {
		wfs_perror("[cmd_sock] bind");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto fail;
	}

	handle->cmd_dest.sin_family = AF_INET;
	handle->cmd_dest.sin_port = htons(WFS_UDP_CMD_PORT);
	handle->cmd_dest.sin_addr.s_addr = inet_addr("127.0.0.1");

	if (connect(handle->cmd_sock, (struct sockaddr *) &handle->cmd_dest,
			sizeof(handle->cmd_dest)) < 0) {
		wfs_perror("[cmd_sock] connect");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto fail;
	}

	/** Event socket */
	handle->evt_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (handle->evt_sock < 0) {
		wfs_perror("[evt_sock] socket");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto fail;
	}

	handle->evt_local.sin_family = AF_INET;
	handle->evt_local.sin_port = 0;
	handle->evt_local.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(handle->evt_sock, (struct sockaddr *) &handle->evt_local,
		sizeof(handle->evt_local)) < 0) {
		wfs_perror("[evt_sock] bind");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto fail;
	}

	handle->evt_dest.sin_family = AF_INET;
	handle->evt_dest.sin_port = htons(WFS_UDP_EVT_PORT);
	handle->evt_dest.sin_addr.s_addr = inet_addr("127.0.0.1");

	if (connect(handle->evt_sock, (struct sockaddr *) &handle->evt_dest,
			sizeof(handle->evt_dest)) < 0) {
		wfs_perror("[evt_sock] connect");
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OS));
		goto fail;
	}

	strncpy(buf, "REGISTER_IF ifname=", sizeof(buf));
	strncat(buf, handle->ifname, IFNAMSIZ);
	cmd  = wfs_create_msg(buf);
	resp = wfs_create_default_msg();
	if (!cmd || !resp) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OOM));
		goto fail;
	}

	/**
	 * Register the new interface through cmd_sock and evt_sock separately
	 */
	ret = _wfs_send(handle->cmd_sock, cmd, resp);
	if (ret != WFS_ERR_SUCCESS) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto fail;
	}
	ret = _wfs_send(handle->evt_sock, cmd, resp);
	if (ret != WFS_ERR_SUCCESS) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto fail;
	}

	goto done;

fail:
	CLOSE(handle->cmd_sock);
	CLOSE(handle->evt_sock);
	FREE(handle);
done:
	FREE(cmd);
	FREE(resp);
out:
	LEAVE();
	return handle;
}


void wfs_disconnect(struct wfs_handle *handle)
{
	struct wfs_msg *cmd = NULL;
	struct wfs_msg *resp = NULL;
	char buf[DEFAULT_MSG_BODY_LEN];
	enum wfs_error ret = WFS_ERR_SUCCESS;

	ENTER();

	if (!handle) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_INVAL));
		goto out;
	}

	memset(buf, 0, sizeof(buf));

	/** Unregister the interface */
	strncpy(buf, "UNREGISTER_IF ifname=", sizeof(buf));
	strncat(buf, handle->ifname, IFNAMSIZ);
	cmd  = wfs_create_msg(buf);
	resp = wfs_create_default_msg();
	if (!cmd || !resp) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(WFS_ERR_OOM));
		goto done;
	}
	if (handle->cmd_sock) {
		ret = _wfs_send(handle->cmd_sock, cmd, resp);
		if (ret != WFS_ERR_SUCCESS) {
			wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
			goto done;
		}
	}
	if (handle->evt_sock) {
		ret = _wfs_send(handle->evt_sock, cmd, resp);
		if (ret != WFS_ERR_SUCCESS) {
			wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
			goto done;
		}
	}

done:
	CLOSE(handle->cmd_sock);
	CLOSE(handle->evt_sock);
	FREE(handle);
	FREE(cmd);
	FREE(resp);
out:
	LEAVE();
}


enum wfs_error wfs_send(struct wfs_handle *handle, const struct wfs_msg *cmd,
		struct wfs_msg *resp)
{
	enum wfs_error ret = WFS_ERR_SUCCESS;

	ENTER();

	/** resp may be NULL if caller does not need the response */
	if (!handle || !cmd) {
		ret = WFS_ERR_INVAL;
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto out;
	}

	ret = _wfs_send(handle->cmd_sock, cmd, resp);

out:
	LEAVE();
	return ret;
}


enum wfs_error wfs_recv(struct wfs_handle *handle, struct wfs_msg *event)
{
	enum wfs_error ret = WFS_ERR_SUCCESS;

	ENTER();

	if (!handle || !event) {
		ret = WFS_ERR_INVAL;
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto out;
	}

	ret = _wfs_recv(handle->evt_sock, event);

out:
	LEAVE();
	return ret;
}


int wfs_get_cmd_sock(struct wfs_handle *handle)
{
	int ret = -1;

	ENTER();

	if (!handle) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto out;
	}

	ret = handle->cmd_sock;

out:
	LEAVE();
	return ret;
}


int wfs_get_evt_sock(struct wfs_handle *handle)
{
	int ret = -1;

	ENTER();

	if (!handle) {
		wfs_printf(LOG_ERR, "%s", wfs_api_err_to_str(ret));
		goto out;
	}

	ret = handle->evt_sock;

out:
	LEAVE();
	return ret;
}
